#include "testbasicvertexbuffer.h"

#include "inputs/manager.h"
#include "geometrybuilder.h"

namespace test {
	namespace graffiti {

bool BasicVertexBuffer::setup(dw::graffiti::RenderWindow* renderWindow)
{
	if (base::setup(renderWindow))
	{
		dw::inputs::InputManager& inputManager = dw::inputs::InputManager::getInstance();
	
		m_wireframe.addCondition(dw::inputs::KeyStateCondition(OIS::KC_F1, dw::inputs::ButtonStateCondition::Pressed));
	
		inputManager.registerCommand(&m_wireframe);

		initPrograms();
		initBasis();
		initTriCube();

		m_timer.reset();

		return true;
	}

	return false;
}

bool BasicVertexBuffer::render()
{
	bool baseSucceed = base::render();

	if (m_wireframe.isActive())
	{
		m_wireframeMode = !m_wireframeMode;
	}

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	if (m_wireframeMode)
	{
		glDisable(GL_CULL_FACE);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
	else
	{
		glEnable(GL_CULL_FACE);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}

	dw::float32 deltaT = (dw::float32)m_timer.getSeconds();
	m_timer.reset();

	m_objectRot = m_objectRot + (deltaT * 90.0f);
	
	dw::graffiti::Renderer& renderer = dw::graffiti::Renderer::getInstance();

	renderer.pushMatrix(dw::graffiti::matrix::proj);
	renderer.setMatrix(dw::graffiti::matrix::proj, getProjectionMatrix());

	renderer.pushMatrix(dw::graffiti::matrix::view);
	renderer.setMatrix(dw::graffiti::matrix::view, getViewMatrix());

	renderer.pushMatrix(dw::graffiti::matrix::model);
	renderer.setMatrix(dw::graffiti::matrix::model, glm::mat4(1.0f));

	//! @name Basis
	//! @{
	renderer.pushMatrix(dw::graffiti::matrix::model);
	renderer.multMatrix(dw::graffiti::matrix::model, glm::mat4(1.0f));

	renderer.setProgram(m_programXformColor);
	renderer.setUniform("ModelViewProj", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::modelviewproj)));

	renderer.setVertexBuffer(m_basisVertexBuffer);
	renderer.setIndexBuffer(m_basisIndexBuffer);
	renderer.drawIndexed(dw::graffiti::primitive::type::lines, 3);
	
	renderer.popMatrix(dw::graffiti::matrix::model);
	//! @}

	//! @name First cube
	//! @{
	glm::mat4 objectRotation = glm::rotate(glm::mat4(1.0f), m_objectRot, glm::vec3(0.0f, 1.0f, 0.0f));
	glm::mat4 objectTranslation = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
	
	renderer.pushMatrix(dw::graffiti::matrix::model);
	renderer.multMatrix(dw::graffiti::matrix::model, objectTranslation);
	renderer.multMatrix(dw::graffiti::matrix::model, objectRotation);

	renderer.setProgram(m_programXformColor);
	renderer.setUniform("ModelViewProj", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::modelviewproj)));

	renderer.setVertexBuffer(m_triCubeVertexBuffer);
	renderer.setIndexBuffer(m_triCubeIndexBuffer);
	renderer.drawIndexed(dw::graffiti::primitive::type::triangles, 12);

	renderer.popMatrix(dw::graffiti::matrix::model);
	//! @}

	//! @name Second cube
	//! @{
	objectRotation = glm::rotate(glm::mat4(1.0f), m_objectRot, glm::vec3(0.0f, 1.0f, 0.0f));
	objectTranslation = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 3.0f));
	
	renderer.pushMatrix(dw::graffiti::matrix::model);
	renderer.multMatrix(dw::graffiti::matrix::model, objectTranslation);
	renderer.multMatrix(dw::graffiti::matrix::model, objectRotation);

	renderer.setProgram(m_programXformColor);
	renderer.setUniform("ModelViewProj", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::modelviewproj)));

	renderer.setVertexBuffer(m_triCubeVertexBuffer);
	renderer.setIndexBuffer(m_triCubeIndexBuffer);

	for (dw::uint32 triangleIt = 0; triangleIt < 12; ++triangleIt)
	{
		renderer.drawIndexedBiased(dw::graffiti::primitive::type::triangles, 1, 0, triangleIt);
	}
	renderer.popMatrix(dw::graffiti::matrix::model);
	//! @}

	renderer.setProgram(NULL);

	renderer.popMatrix(dw::graffiti::matrix::model);
	renderer.popMatrix(dw::graffiti::matrix::view);
	renderer.popMatrix(dw::graffiti::matrix::proj);

	return baseSucceed;
}

bool BasicVertexBuffer::teardown()
{
	dw::inputs::InputManager& inputManager = dw::inputs::InputManager::getInstance();
	
	inputManager.unregisterCommand(&m_wireframe);

	delete m_basisIndexBuffer;
	delete m_basisVertexBuffer;

	delete m_triCubeIndexBuffer;
	delete m_triCubeVertexBuffer;

	delete m_programXformColor;

	return base::teardown();
}

static const dw::graffiti::AttributeDeclaration attributeDeclarations[] = {
	{ dw::graffiti::attribute::semantic::position, 0, dw::graffiti::attribute::type::vec3 },
	{ dw::graffiti::attribute::semantic::color, 0, dw::graffiti::attribute::type::vec3 }
};

bool BasicVertexBuffer::initBasis()
{
	static const dw::uint32 indicesCount = 6;
	static const dw::byte indices[indicesCount] = {
		0, 1, 0, 2, 0, 3
	};

	static const dw::uint32 verticesCount = 4;
	static const glm::vec3 vertices[verticesCount * 2] = {
		glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 0.0f),
		glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(1.0f, 0.0f, 0.0f),
		glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f),
		glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, 1.0f)
	};

	m_basisIndexBuffer = new dw::graffiti::IndexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, dw::graffiti::attribute::type::uint8);
	m_basisIndexBuffer->bind();
	m_basisIndexBuffer->resize(indicesCount);
	m_basisIndexBuffer->setData(indices);
	m_basisIndexBuffer->unbind();

	m_basisVertexBuffer = new dw::graffiti::VertexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, attributeDeclarations, 2);
	m_basisVertexBuffer->bind();
	m_basisVertexBuffer->resize(verticesCount);
	m_basisVertexBuffer->setData(vertices);
	m_basisVertexBuffer->unbind();

	return true;
}

bool BasicVertexBuffer::initTriCube()
{
	dw::uint32 vertexCount;
	void* vertexData;
	dw::uint32 indexCount;
	dw::byte* indexData;

	dw::graffiti::GeometryBuilder::buildFilledCube(attributeDeclarations, 2, vertexData, vertexCount, indexData, indexCount);

	m_triCubeIndexBuffer = new dw::graffiti::IndexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, dw::graffiti::attribute::type::uint8);
	m_triCubeIndexBuffer->bind();
	m_triCubeIndexBuffer->resize(indexCount);
	m_triCubeIndexBuffer->setData(indexData);
	m_triCubeIndexBuffer->unbind();

	m_triCubeVertexBuffer = new dw::graffiti::VertexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, attributeDeclarations, 2);
	m_triCubeVertexBuffer->bind();
	m_triCubeVertexBuffer->resize(vertexCount);
	m_triCubeVertexBuffer->setData(vertexData);
	m_triCubeVertexBuffer->unbind();

	delete [] vertexData;
	delete [] indexData;

	return true;
}

bool BasicVertexBuffer::initPrograms()
{
	static const dw::graffiti::ProgramAttributeDeclaration basisProgramAttributes[] = {
		{ "Position", dw::graffiti::attribute::semantic::position, 0, dw::graffiti::attribute::type::vec3, false },
		{ "Color", dw::graffiti::attribute::semantic::color, 0, dw::graffiti::attribute::type::vec3, false }
	};

	const std::string vsSource = dw::graffiti::loadFile("shaders/vertex_xform_color.vs");
	const char* vsCString = vsSource.c_str();

	dw::graffiti::VertexShader vShader(&vsCString, 1);

	const std::string psSource = dw::graffiti::loadFile("shaders/vertex_xform_color.fs");
	const char* psCString = psSource.c_str();

	dw::graffiti::PixelShader pShader(&psCString, 1);

	m_programXformColor = new dw::graffiti::Program();
	m_programXformColor->attach(vShader);
	m_programXformColor->attach(pShader);
	m_programXformColor->link(basisProgramAttributes, 2);

	return true;
}

	} // namespace graffiti
} // namespace test
